#include <cstdio>
#include <iostream>
#include <fstream>
#include <stdexcept>

#include "sybie/common/Arguments.hh"
#include "sybie/common/Text.hh"

#include "sybie/datain/Generate.hh"

namespace sybie {
namespace datain {
namespace {

class Arguments : public common::ShellArgumentsWithHelp
{
private:
    enum { MinPartSize = 256 };

    static Memos GetInitMemos()
    {
        Memos memos;
        memos.unnamed_arg = "<input filename>";
        return memos;
    }
public:
    Arguments()
        : common::ShellArgumentsWithHelp(GetInitMemos())
    {
        Add(common::Argument("dataid","id",
                             common::WithoutShortName,
                             common::Variant,
                             "data id used in source code.\n"
                             "default=basename(<input filename>)"));
        Add(common::Argument("out","out",'o',common::Variant,
                             "Output file, overwrite it if exists.\n"
                             "default=<id>.cc"));
        Add(common::Argument("noswp","noswp",'n',common::Flag,
                             "Disable swap file, overwrite output file directly.\n"
                             "Useful while output to /dev/stdout"));
        Add(common::Argument("psize","psize",'s',common::Variant,
                             (std::string)"Split size, not less than "
                             + std::to_string(MinPartSize)
                             + ", default = "
                             + std::to_string(DefaultPartSize)));
    }

    virtual ~Arguments() throw() { }

    std::string input_filename() const
    {
        return GetUnnamedArguments()[0];
    }

    std::string output_filename() const
    {
        if (IsSet("out"))
            return Get("out");
        else
            return data_id() + ".cc";
    }

    std::string data_id() const
    {
        if (IsSet("id"))
            return Get("id");
        else
            return common::GetFilename(input_filename());
    }

    bool noswp() const
    {
        return (IsSet("noswp"));
    }

    size_t part_size() const
    {
        if (IsSet("psize"))
            return common::ParseInt(Get("psize").c_str());
        else
            return DefaultPartSize;
    }

protected:
    virtual void CheckArguments() throw(std::invalid_argument)
    {
        common::ShellArgumentsWithHelp::CheckArguments();

        const std::vector<std::string> unnamed_args = GetUnnamedArguments();
        if (!Help() && unnamed_args.size() != 1)
            throw std::invalid_argument("Invalid arguments, get usage with --help.");

        if (part_size() < MinPartSize)
            throw std::invalid_argument((std::string)"psize is smaller than "
                                        + std::to_string(MinPartSize));
    }
}; //class Arguments

void _main(int argc, char** argv) throw(std::exception)
{
    Arguments args;
    args.Parse(argc, argv);

    if (args.Help())
    {
        std::cout<<"Transfer file data into data-in source file."<<std::endl;
        std::cout<<args.GetHelpInformation()<<std::endl;
        return;
    }

    bool noswp = args.noswp();
    const std::string in_filename = args.input_filename();
    const std::string out_filename = args.output_filename();
    const std::string tmp_filename = out_filename + ".swp";
    const std::string data_id = args.data_id();
    const size_t part_size = args.part_size();

    std::ifstream ifs(in_filename, std::ios_base::binary);
    if (!ifs.good())
        throw std::runtime_error((std::string)"Cannot open input file: " + in_filename);

    const std::string os_filename = noswp ? out_filename : tmp_filename;
    std::ofstream ofs(os_filename, std::ios_base::trunc);
    if (!ofs.good())
        throw std::runtime_error((std::string)"Cannot open output file: " + os_filename);

    ofs << "//Generated by DataIn, do not modify." << std::endl
        << "//Generated from " << in_filename << std::endl;
    Generate(ifs, ofs, data_id, part_size);
    ifs.close();
    ofs.close();

    if (!noswp)
    {
        remove(out_filename.c_str()); //ignore error
        if(rename(tmp_filename.c_str(), out_filename.c_str()))
        {
            throw std::runtime_error((std::string)"Failed rename file: "
                                     + tmp_filename + " -> " + out_filename);
        }
    }
}

} //namespace
} //namespace datain
} //namespace sybie

int main(int argc, char** argv)
{
    try
    {
        sybie::datain::_main(argc, argv);
        return 0;
    }
    catch (std::exception& ex)
    {
        std::cerr<<"FATAL: "<<ex.what()<<std::endl;
        return 1;
    }
}
